Lab 02 - Introduction to Python

Introduction to Python programming

IRiM and Fossbot4AI logos

1. Activity Identity

Activity title Introduction to Robotics
Topic Python
Authors Institute of Robotics and Machine Intelligence 
Dominik Belter, Jakub Chudziński, Marcin Czajka, Kamil Młodzikowski
Target learners Bachelor
Estimated duration 1.5 hour
Difficulty level Beginner
FOSSBot environment FOSSBot:v2
Licence CC BY 4.0

2. Learning Objectives and Competences

ID Learning outcome Related competances Assessment evidence
LO1 Students will be able to write, debug, and execute basic Python scripts using variables, loops, conditionals, and functions. Computational thinking / Programming Completed scripts
LO2 Students will be able to set up and manage isolated Python virtual environments (venv). Programming / Software development Setup and management of virtual environments
LO3 Students will be able to perform basic data manipulation and file I/O operations using standard Python libraries and NumPy. Data handling / Computational thinking Source code and output files
LO4 Students will be able to apply Python scripting to issue basic movement to FOSSBot Robot programming Robot control script in Python, optional video

3. Prerequisites

4. Required Material and Setup

Category Item Version / Quantity Notes
Hardware Workstation 1 per group A workstation with Ubuntu installed
Hardware FOSSBot:v2 kit 1 per group Ensure the battery is charged
Software Python & Virtual Env Version recommended for the OS system Check installation instructions in Step 0
Software IDE (e.g., VS Code, PyCharm) Latest version Optional, but recommended for easier coding

5. Safety, Ethics and Accessibility Notes

Battery and wiring safety must be checked before powering the robot.

Make sure there is enough space around the robot to move safely, and that there are no obstacles that could cause damage to the robot or the environment.

Visual Accessibility: IDEs like VS Code and PyCharm support high-contrast themes and font scaling. Students are encouraged to adjust the terminal and editor font sizes using Ctrl + ‘+’ for better readability if needed.

6. Scenario and Problem Statement

Python is one of the most popular programming languages in AI, robotics, and many other fields. In this lab, you will learn the basics of Python programming and how to use it to control a robot. This instruction will cover Python syntax, data structures, and basic programming concepts. However, if you want to learn more about Python, there are many online courses and tutorials (e.g., Harvard CS50’s Introduction to Python or w3schools Python tutorial) that can help you deepen your knowledge.

7. Lab Workflow

Phase Student action Expected output Time
1. Prepare the workspace Initialize environment Ready-to-run folder [5 min]
2. Read about basics of Python Read about basics of Python Basic understanding [10 min]
3. Basic scripts implementation Write code Completed scripts [45 min]
4. Python for robot control Write Python scripts to control the robot Completed scripts [20 min]
5. Reflect Answer synthesis questions Short analysis [10 min]

8. Step-by-Step Instructions

Step 0 - Initial environment preparation [skip if using the workstations in the laboratory]

  1. Install Python using the following commands in terminal:

Update the package list:

sudo apt update

Install the recommended Python3 version for your operating system, with pip (Python package manager) and venv (virtual environment) support:

sudo apt install python3 python3-pip python3-venv

You can check the installed Python version with:

python3 --version
  1. Install an IDE of your choice (e.g., Visual Studio Code or PyCharm) and set it up for Python development.

    • for Visual Studio Code, you can use the following command:
     sudo snap install --classic code

    and then install the Python extension from the marketplace. To open the current folder in VS Code, use: bash code .

    • for PyCharm, you can use the following command:
    sudo snap install pycharm-community --classic

    Then, open PyCharm and create a new project, selecting the Python interpreter you installed in step 1.

Step 1 - Environment preparation

  1. In case the students before haven’t removed the directory related to this instruction, you should remove it:
rm -rf fossbot-python-lab
  1. Create a new directory for this project and navigate into it:
mkdir fossbot-python-lab
cd fossbot-python-lab
  1. Create a new Python virtual environment for this project:
python3 -m venv fossbot-env
  1. Activate the virtual environment (this ensures that any Python packages you install will be isolated to this project):
source fossbot-env/bin/activate

Expected result:: - A new directory named fossbot-python-lab is created and you are inside it. - A new virtual environment named fossbot-env is created and activated. - Your terminal prompt should now indicate that you are in the fossbot-env virtual environment (e.g., it may show (fossbot-env) at the beginning of the prompt).

Step 2 - Basics of Python programming

Below are the basic information about Python syntax and programming concepts. Please read through the following section. Because Python is a script-based language, the code is executed line by line, and it is not compiled like in C++ or Java.

  1. Indentation: Python uses indentation to define code blocks. For example, a function definition looks like this:
def my_function():
    print("Hello, World!")

the code inside the function is indented, and this indicates that it belongs to the function.

Note: In Python, it is important to use consistent indentation (e.g., 4 spaces) to avoid syntax errors.

⚠️ Examples of wrong indentation:

# No indentation - this will cause a syntax error
def my_function():
print("Hello, World!")
# Inconsistent indentation - this will also cause a syntax error
def my_function():
    print("Hello, World!")
  print("This line has different indentation")
  1. Variables and data types: In Python, you can store values in variables. Because Python is dynamically typed, you don’t need to declare the type of a variable (opposite to statically typed languages like C++ or Java). For example:
x = 10             # An integer
y = 2.718          # A float
name = "FOSSBot"   # A string
is_a_robot = True  # A boolean

Also you can overwrite the value of a variable with a different type:

x = 10                 # x is an integer
x = "Hello, FOSSBot!"  # x is now a string

Basic algebraic operations that you can perform:

a = 5
b = 3
sum_result = a + b       # Addition (8)
diff_result = a - b      # Subtraction (2)
prod_result = a * b      # Multiplication (15)
quot_result = a / b      # Division (1.666...)
mod_result = a % b       # Modulo (2)
pow_result = a ** b      # Exponentiation (125)
div_result = a // b      # Floor division (1)

And you can also use the operations with variables of different types:

x = 10                 # x is an integer
y = 2.718              # y is a float
result = x * y         # result is a float (27.18)
  1. Data structures: To store multiple values, you can use data structures like lists, tuples, and dictionaries:
# A list is an ordered collection of items
my_list = [1, 2, 3, 4, 5]

# A tuple is an ordered, immutable collection of items 
# (you cannot change the values in a tuple after it is created)
my_tuple = (1, 2, 3, 4, 5)

# A dictionary is a collection of key-value pairs
my_dict = {"name": "FOSSBot", "type": "robot", "number_of_wheels": 2}

To access the values in the abovementioned data structures, you can use indexing (for lists and tuples) or keys (for dictionaries):

print(my_list[0])       # Output: 1
print(my_tuple[1])      # Output: 2
print(my_dict["name"])  # Output: FOSSBot

To add new items to a list, you can use the append() method:

my_list.append(6)       # my_list is now [1, 2, 3, 4, 5, 6]

And to add a new key-value pair to a dictionary, you can simply assign a value to a new key:

my_dict["color"] = "blue"  
# my_dict is now: 
# {"name": "FOSSBot", "type": "robot", "number_of_wheels": 2, "color": "blue"}
  1. Conditional statements: You can use if, elif, and else statements to perform different actions:
distance_to_target = 10

if distance_to_target > 50:
    print("The robot is far away. Get closer!")
elif distance_to_target < 5:
    print("The robot is close. Stop!")
else:
    print("The robot is at a moderate distance. Keep going!")

You can also use logical operators (and, or, not) to combine conditions:

is_obstacle_detected = True
is_battery_low = False

if not is_obstacle_detected and not is_battery_low:
    print("The robot can move forward.")
elif is_battery_low:
    print("The robot should recharge.")
else:
    print("The robot should avoid the obstacle.")
  1. Loops: You can use for and while loops to repeat actions:
# A while loop is executed as long as a condition is true
i = 0
while i < 5:
    print(f"Counter: {i}")
    i += 1  # This is equivalent to i = i + 1
# A for loop iterates over some sequence (like a list or a range of numbers)
for i in range(5):  # This will iterate over the numbers 0, 1, 2, 3, 4
    print(f"Counter: {i}")

fossbot_colors = ["blue", "red", "green"]
for color in fossbot_colors:  # This will iterate over the items in the list
    print(f"Color: {color}")

Note: The range() function generates a sequence of numbers. It can take one, two, or three arguments: range(stop), range(start, stop), or range(start, stop, step). For example, range(5) generates the numbers 0 to 4, while range(1, 6) generates the numbers 1 to 5, and range(1, 10, 3) generates the numbers 1, 4, 7.

  1. Functions: If you have a part of code that you want to reuse multiple times, you can define a function, so it can be called whenever needed:
def greet(name):
    print(f"Hello, {name}!")

greet("FOSSBot")  # Output: Hello, FOSSBot!

Note: The f before the string in the print() function allows you to use f-strings, which are a convenient way to format strings with variables. But you can also concatenate strings using the + operator:

def greet(name):
    print("Hello, " + name + "!")

greet("FOSSBot")  # Output: Hello, FOSSBot!

You can also return values from a function using the return statement:

def add(a, b):
    return a + b

result = add(5, 3)  # result is 8

Moreover, you can set default values for function parameters. This means that if you don’t provide a value for that parameter, it will use the default value:

def greet(name, number_of_greetings=1):
    for i in range(number_of_greetings):
        print(f"Hello, {name}!")

greet("FOSSBot")  
# Output: Hello, FOSSBot!

greet("FOSSBot", 3)
# Output:
# Hello, FOSSBot!
# Hello, FOSSBot!
# Hello, FOSSBot!

Note: You can also set the variable types in the function definition, which can help with code readability and debugging, but is optional in Python:

def greet(name: str, number_of_greetings: int = 1) -> None:
    for i in range(number_of_greetings):
        print(f"Hello, {name}!")

The -> None indicates that this function does not return any value. If the function were to return a value, you would replace None with the type of the returned value (e.g., -> str if it returns a string).

  1. Saving and loading data from files: You can use the built-in open() function to read from and write to files:
# Writing to a file
with open("output.txt", "w") as file:
    file.write("Hello, FOSSBot!\n")

# If you want to write multiple lines, you can use a list of strings and the writelines() method:
lines = ["Line 1\n", "Line 2\n", "Line 3\n"]
with open("output.txt", "w") as file:
    file.writelines(lines)

#If you don't want to overwrite the existing content of the file, you can open it in append mode:
with open("output.txt", "a") as file:
    file.write("This line will be added to the end of the file.\n")

Note: The \n at the end of the string indicates a new line.

# Reading from a file
with open("output.txt", "r") as file:
    data_from_file = file.read()
  1. Importing libraries: To use additional functionalities, you can import libraries. There are many libraries available for Python that can help you with various tasks, such as machine learning, robot control and more. For example, to use the popular NumPy library that provides support for multidimensional arrays and mathematical functions, you can install it using pip and then import it in your code:
pip install numpy

If you need a specific version of the library, you can set install it like this:

pip install numpy==1.21.0

Then, in your Python code, you can import the library:

import numpy

some_array = numpy.array([1, 2, 3, 4, 5])
mean_value = numpy.mean(some_array)

Or you can import it with an alias for easier use:

import numpy as np

some_array = np.array([1, 2, 3, 4, 5])
mean_value = np.mean(some_array)

It is also possible to import only specific functions or classes from a library:

from numpy import array, mean

some_array = array([1, 2, 3, 4, 5])
mean_value = mean(some_array)
  1. Classes and objects: Python is an object-oriented language, what means that you can create your own data types and instances of those data types. You can define a class using the class keyword, and then create objects (instances) of that class:
class Robot:
    # __init__ is a special method 
    # that is called when an object is created
    def __init__(self, name, number_of_wheels):
        # To define attributes of the class, you can use the self keyword
        self.name = name
        self.number_of_wheels = number_of_wheels

    # When defining methods of the class, 
    # you also need to use the self keyword as the first parameter
    def greet(self):
        print(f"Hello, I am {self.name} and I have {self.number_of_wheels} wheels.")

    # You can also define methods that take additional parameters
    def move(self, distance):
        print(f"{self.name} is moving {distance} meters.")

my_robot = Robot("FOSSBot", 2)
my_robot.greet()  # Output: Hello, I am FOSSBot and I have 2 wheels.
my_robot.move(10)  # Output: FOSSBot is moving 10 meters.

Step 3 - Tasks: Write scripts

In this part, you will write some basic Python scripts to practice the concepts you have learned. Inside the fossbot-python-lab directory, create a new file for each task (e.g., task1.py, task2.py) and open them in your IDE. Use the following code snippet as a template for your scripts:

def main():
    # Your code here
    pass  # This is a placeholder that does nothing. Remove it when you add your code.

# This ensures that the main() function is called when you run the script
if __name__ == "__main__":
    main()
  1. Generate an array of 100 random integers between 1 and 100. Save the array to a text file, with one item per line. You can use the random module for this and the randrange() function. Check the documentation for more information https://docs.python.org/3/library/random.html. Then, calculate and print the mean of the numbers in the array. Do this using 2 approaches:

Print the results in the console.

  1. Load the text file from task 1. Find the most common number in the file and how many times it appears. Print the result in the console.

  2. Generate an array of integers from 1 to 100 using the range() function. Do the following operations on the array:

Print the resulting array. This task is based on the classic FizzBuzz problem (e.g., https://leetcode.com/problems/fizz-buzz/description/)

  1. Prepare a script that will take two inputs from user (use the input() function - documentation) - a string to encrypt and a shift value. Add validation to ensure the shift value is an integer. Write a function that will implement the Caesar cipher to encrypt the input string. Read more about the Caesar cipher here: https://en.wikipedia.org/wiki/Caesar_cipher.

  2. Prepare a script that will take an integer input from user. Then, write a function that will calculate the Digital Root of the input number. The Digital Root is the recursive sum of all the digits in a number until it becomes a single digit. For example, the Digital Root of 23 is 5 (2 + 3 = 5), and the Digital Root of 999 is 9 (9 + 9 + 9 = 27, then 2 + 7 = 9). Read more about Digital Root here: https://en.wikipedia.org/wiki/Digital_root. Use a generator to implement the solution with the yield keyword to generate each digit (more about generators: https://wiki.python.org/moin/Generators).

Expected result: You should have 5 Python scripts that implement the tasks described above.

Step 4 - How to use Python for robot control

  1. Connect to the robot using ssh:
ssh student@<robot_ip_address>
  1. Create a new directory on the robot to store your Python scripts and enter it.

  2. Write a code to make the robot move.

Tip: To write the code you can use one of the following approaches: a text editor on the robot (e.g., nano, vim) or create the code on your workstation and then transfer it to the robot using scp command.

Use the following code snippet:

#!/usr/bin/env python3

# Imports
import time
import lgpio

# Pin configuration
MOTOR_STANDBY = 6
MOTOR_A_PWM = 12
MOTOR_A_IN1 = 19
MOTOR_A_IN2 = 26
MOTOR_B_PWM = 13
MOTOR_B_IN1 = 5
MOTOR_B_IN2 = 0

# Set the frequency for PWM (Hz)
FREQUENCY = 1000

# Set the duty cycle for PWM (0-100)
DUTY_CYCLE = 50

class Robot:
    def __init__(self, chip=4, freq=1000, pins=None):
        self.handle = lgpio.gpiochip_open(chip)
        self.freq = freq
        if pins is None:
            raise ValueError("Pins must be provided")
        self.pins = pins
        
        # Claim all GPIO pins as outputs
        # This is necessary to control the motors and other components
        for pin in self.pins:
            lgpio.gpio_claim_output(self.handle, pin, 0)
        # Set the standby pin high to enable the motors
        lgpio.gpio_write(self.handle, MOTOR_STANDBY, 1)
        print("Robot initialized. Motors are enabled.")

    def _set_motor(self, pwm, in1, in2, direction, duty):
        # direction: 1 = forward, -1 = backward, 0 = stop
        # We need to set the IN1 and IN2 pins to control the direction,
        # and then set the PWM pin to control the speed
        if direction == -1:
            lgpio.gpio_write(self.handle, in1, 1)
            lgpio.gpio_write(self.handle, in2, 0)
            lgpio.tx_pwm(self.handle, pwm, self.freq, duty)
        elif direction == 1:
            lgpio.gpio_write(self.handle, in1, 0)
            lgpio.gpio_write(self.handle, in2, 1)
            lgpio.tx_pwm(self.handle, pwm, self.freq, duty)
        else:
            lgpio.tx_pwm(self.handle, pwm, self.freq, 0)
            lgpio.gpio_write(self.handle, in1, 0)
            lgpio.gpio_write(self.handle, in2, 0)

    def move_forward(self, duty=50):
        # Remove the NotImplementedError and implement the logic to move the robot forward
        raise NotImplementedError("This method should be implemented to move the robot forward.")

    def move_backward(self, duty=50):
        # Remove the NotImplementedError and implement the logic to move the robot backward
        raise NotImplementedError("This method should be implemented to move the robot backward.")

    def turn_left(self, duty=50):
        # Remove the NotImplementedError and implement the logic to turn the robot left
        raise NotImplementedError("This method should be implemented to turn the robot left.")

    def turn_right(self, duty=50):
        # Remove the NotImplementedError and implement the logic to turn the robot right
        raise NotImplementedError("This method should be implemented to turn the robot right.")

    def stop(self):
        self._set_motor(MOTOR_A_PWM, MOTOR_A_IN1, MOTOR_A_IN2, 0, 0)
        self._set_motor(MOTOR_B_PWM, MOTOR_B_IN1, MOTOR_B_IN2, 0, 0)

    def cleanup(self):
        # Stop the robot and free the GPIO pins
        self.stop()
        lgpio.gpio_write(self.handle, MOTOR_STANDBY, 0)
        for pin in self.pins:
            try: lgpio.gpio_free(self.handle, pin)
            except: pass
        # Close the GPIO chip handle
        lgpio.gpiochip_close(self.handle)
        print("Robot cleanup completed. Motors are disabled and GPIO pins are freed.")

if __name__ == "__main__":
    try:
        pins = [MOTOR_STANDBY, MOTOR_A_PWM, MOTOR_A_IN1, MOTOR_A_IN2, MOTOR_B_PWM, MOTOR_B_IN1, MOTOR_B_IN2]
        robot = Robot(chip=4, freq=FREQUENCY, pins=pins)

        # Move the robot forward for 2 seconds
        print("Moving forward...")
        robot.move_forward(duty=DUTY_CYCLE)
        time.sleep(2)
        robot.stop()

        # Move the robot backward for 2 seconds
        print("Moving backward...")
        robot.move_backward(duty=DUTY_CYCLE)
        time.sleep(2)
        robot.stop()

        # Rotate the robot left for 1 second
        print("Rotating left...")
        robot.turn_left(duty=DUTY_CYCLE)
        time.sleep(1)
        robot.stop()

        # Rotate the robot right for 1 second
        print("Rotating right...")
        robot.turn_right(duty=DUTY_CYCLE)
        time.sleep(1)
        robot.stop()

    finally:
        robot.cleanup()
  1. Create another script to read data from the LiDAR sensor and print the minimum distance value. You can use the rplidar-roboticia library for this. Install it using pip:
pip install rplidar-roboticia

Use the following code snippet:

#!/usr/bin/env python3
from rplidar import RPLidar

PORT_NAME = '/dev/ttyUSB0'

# Initialize the RPLidar object
lidar = RPLidar(PORT_NAME)

try:
    # Print device information
    print("Info:", lidar.get_info())
    print("Health:", lidar.get_health())

    print("Starting scanning...")
    max_scans = 10
    i = 0

    for scan in lidar.iter_scans():
        print(f"Scan contains {len(scan)} measurements")

        # Each measurement in the scan is a tuple of (quality, angle, distance)
        # Quality represents the signal quality of the measurement,
        # Angle is the angle of the measurement in degrees,
        # and Distance is the distance to the object in millimeters.
        min_distance = float("inf")
        min_distance_angle = None
        for quality, angle, distance in scan:
            raise NotImplementedError("Implement the logic to find the minimum distance and its corresponding angle.")

        if min_distance_angle is None:
            print("No valid measurements in this scan.")
        else:
            print(f"Minimum distance in this scan: {min_distance:.1f} mm at angle {min_distance_angle:.1f} degrees")
        
        i += 1
        if i >= max_scans:
            break

except KeyboardInterrupt:
    print("Stopping...")

finally:
    lidar.stop()
    lidar.stop_motor()
    lidar.disconnect()

Make a screenshot of the output of the script and include it in your submission.

  1. Download a script to save the LiDAR data to a csv file (TODO: Add link). Run it on the robot and check the output file. Copy the output file to your workstation and create a new Python script (on the workstation). Prepare a script that will read the csv file using the pandas library, will calculate the x and y coordinates of the points using the angle and distance values with numpy and will plot the points using the matplotlib library. You can install the libraries using pip:
pip install pandas matplotlib

Below is a code snippet that you can use as a template for your script:

#!/usr/bin/env python3
import pandas as pd
from matplotlib import pyplot as plt
import numpy as np

if __name__ == "__main__":
    # Read the csv file into a pandas dataframe
    df = pd.read_csv('lidar_data.csv')

    # Print the first 5 rows of the dataframe
    print(df.head(5))

    # We select a specific scan number to visualize. You can change the value of `scan_id` to visualize different scans.
    scan_id = 5

    # Filter the dataframe to get only the rows corresponding to the selected scan number
    scan_data = df[df['scan_num'] == scan_id]

    ### Implement the logic to calculate the x and y coordinates of the points using the angle and distance values. Use numpy for calculations.

Tip: To calculate the x and y coordinates from the angle and distance values, you can use the following formulas: x = distance * cos(angle) and y = distance * sin(angle). Make sure to convert the angle from degrees to radians before using the trigonometric functions. You can use the numpy.radians() function for this conversion.

Tip: To create new columns in the pandas dataframe for the x and y coordinates, you can use the following syntax: df['x'] = ... and df['y'] = ....

Tip: To plot the points using matplotlib, you can use the plt.scatter() function. You can also set the aspect ratio of the plot to be equal using plt.axis('equal') to ensure that the x and y axes are scaled equally.

Save the script and run it. You should see a plot of the points corresponding to the selected scan number. Save the plot as an image file (e.g., scan_plot.png) and include it in your submission.

Expected result: Scripts that control the robot (the robot should move forward, backward, and rotate left and right), a script that reads data from the LiDAR sensor and prints the minimum distance value, a csv file with LiDAR data, and a script that reads the csv file, calculates the x and y coordinates of the points, and plots them. An example of the plot is available below. Example plot of LiDAR data

9. Analysis Questions

  1. What are the advantages and disadvantages of using Python for robotics programming compared to other programming languages (e.g., C++ or languages for specific robots)?

  2. In Step 3, you saved and loaded generated data to a text file. Why is file I/O (Input/Output) an important skill when working with robot sensors? Give a practical example of how you might use this during a robotics experiment.

  3. What errors did you encounter while writing the scripts, and how did you debug them? Describe one specific error and what you learned from it.

10. Submission Requirements

11. References and Open Licence

The Creative Commons Attribution 4.0 International (CC BY 4.0) license allows users to share, copy, distribute, and adapt the work, even for commercial purposes, as long as proper credit is given to the original creator.

EU funding disclaimer

Funded by the European Union. Views and opinions expressed are however those of the author(s) only and do not necessarily reflect those of the European Union or the European Education and Culture Executive Agency (EACEA). Neither the European Union nor EACEA can be held responsible for them.